package org.vacoor.nothing.web.spring.inject;

import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.Map;

import static org.vacoor.nothing.common.reflect.TypeResolver.canAcceptType;
import static org.vacoor.nothing.common.reflect.TypeResolver.resolveType;
import static org.vacoor.nothing.common.reflect.TypeUtils.getTypeName;

/**
 * TODO 好像会引起 @PostConstruct 每初始化一个 bean 都执行一次, 但是 InitializingBean 不受影响
 *
 * 用来处理泛型类型注入的 BeanPostProcessor
 * 仅支持字段上的 @GenericAutowire
 * 注: 仅仅只是为了注入泛型, 还很不完善, 不应该用来注入其他类型
 * spring 4 已经支持泛型注入, 可以抛弃该类
 * <p/>
 *
 * @author vacoor
 */
public class GenericAutowiredAnnotationBeanPostProcessor implements BeanPostProcessor, PriorityOrdered, BeanFactoryAware {
    private static final Logger LOG = LoggerFactory.getLogger(GenericAutowiredAnnotationBeanPostProcessor.class);
    private ConfigurableListableBeanFactory beanFactory;

    /**
     * 放在初始化之前是因为 afterPropertiesSet(init) 方法在 postProcessAfterInitialization 之后
     * afterPropertiesSet 中可能会用到注入的对象
     *
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Class<?> contextType = bean.getClass();

        for (Class<?> clazz = contextType; clazz != Object.class; clazz = clazz.getSuperclass()) {
            for (Field field : clazz.getDeclaredFields()) {
                if (!field.isAnnotationPresent(GenericAutowired.class)) {
                    continue;
                }
                ReflectionUtils.makeAccessible(field);
                Type acceptedGenericType = field.getGenericType();   // 字段泛型类型
                Class<?> acceptedRawType = field.getType();          // 字段原始类型

                // 不允许处理非泛型
                if (acceptedGenericType == acceptedRawType) {
                    throw new BeanCreationException("@GenericAutowire is unstable and should only be used to inject decode generic types");
                }

                if (LOG.isDebugEnabled()) {
                    LOG.debug("find generic type [{}#{}] candidates...", contextType, getTypeName(resolveType(contextType, acceptedGenericType)));
                }
                Map<String, ?> candidates = findGenericAutowireCandidates(contextType, acceptedGenericType, acceptedRawType);
                if (1 == candidates.size()) {
                    Map.Entry<String, ?> candidate = candidates.entrySet().iterator().next();
                    try {
                        Object value = candidate.getValue();
                        if (LOG.isDebugEnabled()) {
                            Class<?> type = value.getClass();
                            LOG.debug("inject {} [extends/implements {}] to {}",
                                    type.getName(),
                                    type.isInterface() ? type.getGenericInterfaces() : type.getGenericSuperclass(),
                                    getTypeName(resolveType(contextType, acceptedGenericType)));
                        }

                        field.set(bean, candidate.getValue());
                        continue;
                    } catch (IllegalAccessException ex) {
                        throw new BeanCreationException("Injection of autowired dependencies failed for class [" + clazz + "]", ex);
                    }
                }
                String type = getTypeName(resolveType(contextType, acceptedGenericType));
                if (1 < candidates.size()) { // 多于1个
                    throw new NoUniqueMatchedBeanDefinitionException(type, candidates.keySet());
                }
                // 少于1个
                throw new NoSuchMatchedBeanDefinitionException(type, "expected at least 1 bean which qualifies as" +
                        " autowire candidate for this dependency. Dependency annotations: " +
                        ObjectUtils.nullSafeToString(GenericAutowired.class)
                );

            }
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    protected <T> Map<String, T> findGenericAutowireCandidates(Class<?> context, Type acceptedGenericType, Class<T> acceptedRawType) {
        if (beanFactory == null) {
            throw new IllegalStateException("No BeanFactory configured - override the getBeanOfType method or specify the 'beanFactory' property");
        }
        Map<String, T> beanMap = BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory, acceptedRawType);
        Map<String, T> ancestors = Maps.newHashMap();


        for (Map.Entry<String, T> ancestor : beanMap.entrySet()) {
            String beanName = ancestor.getKey();
            T bean = ancestor.getValue();
            if (canAcceptType(context, acceptedGenericType, bean.getClass())) {
                ancestors.put(beanName, bean);
            }
        }
        return Collections.unmodifiableMap(ancestors);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
            throw new IllegalArgumentException("GenericAutowiredAnnotationBeanPostProcessor requires decode ConfigurableListableBeanFactory");
        }
        this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}
